package net.dotool.geo;

import java.io.Serializable;

public final class LatLng implements Serializable {
	private static final long serialVersionUID = -6260303417986448895L;

	/**
	 * Latitude in degrees.
	 */
	private double latitude;

	/**
	 * Longitude in degrees.
	 */
	private double longitude;

	public LatLng(final double latitude, final double longitude) {
		this.latitude = latitude;
		this.longitude = longitude;
	}

	/**
	 * Convert this latitude and longitude to a UTM reference.
	 * 
	 * @return the converted UTM reference.
	 * @throws NotDefinedOnUTMGridException
	 *             if an attempt is made to convert a LatLng that falls outside the area covered by the UTM grid. The UTM grid is only
	 *             defined for latitudes south of 84&deg;N and north of 80&deg;S.
	 * @since 1.0
	 */
	public UTMRef toUTMRef(final int longitudeZone) {
		if (getLatitude() < -80 || getLatitude() > 84) {
			throw new IllegalArgumentException("Latitude (" + getLatitude() + ") falls outside the UTM grid.");
		}
		if (this.longitude == 180.0) {
			this.longitude = -180.0;
		}
		final Ellipsoid wgsEllipsoid = EllipsoidDefs.getEllipsoid(23);
		final double a = wgsEllipsoid.getEquatorialRadius();
		final double eSquared = wgsEllipsoid.getSquareOfEccentricity();
		final double longitude = this.longitude;
		final double latitude = this.latitude;
		final double latitudeRad = latitude * Math.PI / 180.0;
		final double longitudeRad = longitude * Math.PI / 180.0;
		final double longitudeOrigin = (longitudeZone - 1) * 6 - 180 + 3;
		final double longitudeOriginRad = longitudeOrigin * Math.PI / 180.0;
		final char UTMZone = UTMRef.getUTMLatitudeZoneLetter(latitude);
		final double ePrimeSquared = eSquared / (1 - eSquared);
		final double n = a / Math.sqrt(1 - eSquared * Math.sin(latitudeRad) * Math.sin(latitudeRad));
		final double t = Math.tan(latitudeRad) * Math.tan(latitudeRad);
		final double c = ePrimeSquared * Math.cos(latitudeRad) * Math.cos(latitudeRad);
		final double A = Math.cos(latitudeRad) * (longitudeRad - longitudeOriginRad);
		final double M = a
				* ((1 - eSquared / 4 - 3 * eSquared * eSquared / 64 - 5 * eSquared * eSquared * eSquared / 256) * latitudeRad
						- (3 * eSquared / 8 + 3 * eSquared * eSquared / 32 + 45 * eSquared * eSquared * eSquared / 1024) * Math.sin(2 * latitudeRad)
						+ (15 * eSquared * eSquared / 256 + 45 * eSquared * eSquared * eSquared / 1024) * Math.sin(4 * latitudeRad) - 35 * eSquared
						* eSquared * eSquared / 3072 * Math.sin(6 * latitudeRad));
		final double UTMEasting = GeoUtils.SCALE_CENTRALMERIDIAN * n
				* (A + (1 - t + c) * Math.pow(A, 3.0) / 6 + (5 - 18 * t + t * t + 72 * c - 58 * ePrimeSquared) * Math.pow(A, 5.0) / 120) + 500000.0;
		double UTMNorthing = GeoUtils.SCALE_CENTRALMERIDIAN
				* (M + n
						* Math.tan(latitudeRad)
						* (A * A / 2 + (5 - t + 9 * c + 4 * c * c) * Math.pow(A, 4.0) / 24 + (61 - 58 * t + t * t + 600 * c - 330 * ePrimeSquared)
								* Math.pow(A, 6.0) / 720));
		// Adjust for the southern hemisphere
		if (latitude < 0) {
			UTMNorthing += 10000000.0;
		}
		return new UTMRef(longitudeZone, UTMZone, UTMEasting, UTMNorthing);

	}

	/**
	 * Convert this latitude and longitude to a UTM reference.
	 * 
	 * @return the converted UTM reference.
	 * @throws NotDefinedOnUTMGridException
	 *             if an attempt is made to convert a LatLng that falls outside the area covered by the UTM grid. The UTM grid is only
	 *             defined for latitudes south of 84&deg;N and north of 80&deg;S.
	 * @since 1.0
	 */
	public UTMRef toUTMRef() {
		final int longitudeZone = LatLng.getLongitudeZone(latitude, longitude);
		return toUTMRef(longitudeZone);
	}
//����	
//	j : 116.472119,
//	w : 39.994031,
//	utm_x : 12965758.055149661,
//	utm_y : 4837355.044896257,
//	x : 1139060,
//	y : 1089716
	
//����
//	j	113.308347
//	utm_x	12613568.74
//	utm_y	2628975.75
//	w	23.114845
//	x	1098200
//	y	1104208
	
//	�Ϻ�
//	j	121.529343
//	utm_x	13528731.78
//	utm_y	3638713.59
//	w	31.217769
//	x	1072696
//	y	1079064
	
	
	
	public static void main(String[] args) {
//		LatLng a = new LatLng(39.919768, 116.364070);
//		UTMRef ref = a.toUTMRef();
//		System.out.println( ref.getEasting() + "/" + ref.getNorthing());
//		
		

		LatLng a = new LatLng(39.994031,116.472119);
		UTMRef ref = a.toUTMRef(50);
		System.out.println( ref.getEasting() + "/" + ref.getNorthing());

		a = new LatLng(23.114845,113.308347);
		ref = a.toUTMRef(50);
		System.out.println( ref.getEasting() + "/" + ref.getNorthing());

		a = new LatLng(31.217769,121.529343);
		ref = a.toUTMRef(50);
		System.out.println( ref.getEasting() + "/" + ref.getNorthing());
		
//		UTMRef ref = new UTMRef(1,'Q',12965758.055149661,4837355.044896257);
//		LatLng ll = ref.toLatLng();
//		System.out.println(ll.latitude + "/" + ll.longitude);
	}

	public static final int getLongitudeZone(final double latitude, final double longitude) {
		int longitudeZone = (int) Math.floor((longitude + 180.0) / 6.0) + 1;
		// Special zone for Norway
		if (latitude >= 56.0 && latitude < 64.0 && longitude >= 3.0 && longitude < 12.0) {
			longitudeZone = 32;
		}
		// Special zones for Svalbard
		if (latitude >= 72.0 && latitude < 84.0) {
			if (longitude >= 0.0 && longitude < 9.0) {
				longitudeZone = 31;
			} else if (longitude >= 9.0 && longitude < 21.0) {
				longitudeZone = 33;
			} else if (longitude >= 21.0 && longitude < 33.0) {
				longitudeZone = 35;
			} else if (longitude >= 33.0 && longitude < 42.0) {
				longitudeZone = 37;
			}
		}
		return longitudeZone;
	}

	/**
	 * @return the latitude
	 */
	public double getLatitude() {
		return latitude;
	}

	/**
	 * @param latitude
	 *            the latitude to set
	 */
	public void setLatitude(final double latitude) {
		this.latitude = latitude;
	}

	/**
	 * @return the longitude
	 */
	public double getLongitude() {
		return longitude;
	}

	/**
	 * @param longitude
	 *            the longitude to set
	 */
	public void setLongitude(final double longitude) {
		this.longitude = longitude;
	}

	@Override
	public String toString() {
		return "(" + this.latitude + ", " + this.longitude + ")";
	}
}
